/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.kurvykurvy.page;

import com.kurvykurvy.image.JavaImageUtils;
import com.kurvykurvy.ui.dnd.DragAndDropLock;
import com.kurvykurvy.ui.dnd.GhostGlassPane;
import com.kurvykurvy.ui.shadow.ShadowFactory;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DragSourceMotionListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.lang.reflect.Field;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import org.openide.util.Exceptions;
import org.openide.windows.WindowManager;

/**
 *
 * @author HY
 */
public class PageTemplateSlide
        extends JComponent
        implements DragGestureListener, DragSourceMotionListener, DragSourceListener
{

    private static final Insets INNER_MARGIN = new Insets( 6, 6, 6, 6 );
    private static final Insets OUTER_MARGIN = new Insets( 10, 10, 10, 10 );
    private static final int ITEM_WIDTH = 140;
    private static final int ITEM_HEIGHT = 140;
    private static final int CLIP_HEIGHT = ITEM_HEIGHT;
    private static final int SHADOW_SIZE = 10;
    public static BufferedImage shadowBuffer;
    PageLayoutTemplate pageLayout;
    BufferedImage buffer;
    BufferedImage thumbnail;
    RenderingHints hints;
    double aspectRatio;

    private PageTemplateSlide()
    {
        super();
        this.setBackground( Color.WHITE );
        createRenderingHints();
    }

    public PageTemplateSlide( PageLayoutTemplate layout )
    {
        this();
        this.pageLayout = layout;
        initSlide();
    }

    protected void initSlide()
    {
        if ( this.pageLayout != null && this.pageLayout.getPreviewImage() != null )
        {
            BufferedImage img = this.pageLayout.getPreviewImage();
            aspectRatio = (double) img.getWidth()/ (double) img.getHeight() ;
        }

        DragSource dragSource = DragSource.getDefaultDragSource();
        dragSource.createDefaultDragGestureRecognizer( this, DnDConstants.ACTION_COPY, this );
        dragSource.addDragSourceMotionListener( this );
    }

    private void createRenderingHints()
    {
        hints = new RenderingHints( RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_BICUBIC );
        Object value = RenderingHints.VALUE_TEXT_ANTIALIAS_ON;
        try
        {
            Field declaredField = RenderingHints.class.getDeclaredField( "VALUE_TEXT_ANTIALIAS_LCD_HRGB" );
            value = declaredField.get( null );
        }
        catch ( Exception e )
        {
            Exceptions.printStackTrace( e );
        }
        hints.put( RenderingHints.KEY_TEXT_ANTIALIASING, value );
    }

    public static int getItemWidth()
    {
        return ITEM_WIDTH + INNER_MARGIN.left + INNER_MARGIN.right + OUTER_MARGIN.left + OUTER_MARGIN.right;
    }

    public static int getItemHeight()
    {
        return ITEM_HEIGHT + INNER_MARGIN.top + INNER_MARGIN.bottom + OUTER_MARGIN.top + OUTER_MARGIN.bottom;
    }

    @Override
    public Dimension getPreferredSize()
    {
        return new Dimension( getItemWidth(), getItemHeight() );
    }

    private void renderOffscreen()
    {
        buffer = new BufferedImage( getItemWidth(), getItemHeight(), BufferedImage.TYPE_INT_ARGB );

        int left = OUTER_MARGIN.left;
        int top = OUTER_MARGIN.top;
        int width = ITEM_WIDTH + INNER_MARGIN.left + INNER_MARGIN.right;
        int height = ITEM_HEIGHT + INNER_MARGIN.top + INNER_MARGIN.bottom;

        Graphics2D g2 = buffer.createGraphics();
        g2.setRenderingHints( hints );

        g2.drawImage( getPhotoBackground(),
                OUTER_MARGIN.left - 3 - SHADOW_SIZE / 2, OUTER_MARGIN.top - 3,
                getPhotoBackground().getWidth() + 6, getPhotoBackground().getHeight() + 6, null );

        g2.fillRect( left, top, width, height );

        left += INNER_MARGIN.left;
        top += INNER_MARGIN.top;
        width = ITEM_WIDTH;
        height = (int) (width / aspectRatio);
        System.out.println( "Page Slide : " + height );
        System.out.println( "Page Slide Aspect : " + aspectRatio );
        System.out.println( "Page Slide Top : " + top );

        g2.setClip( left, top, width, CLIP_HEIGHT );
        if ( aspectRatio < 1.0 )
        {
            top -= (int) ((height - CLIP_HEIGHT) / 2.0);
        }

        thumbnail = JavaImageUtils.createThumbnail( this.pageLayout.getPreviewImage(), width );
        g2.drawImage( thumbnail, left, top, null );
        g2.dispose();
    }

    public BufferedImage getThumbnail()
    {
        if ( thumbnail == null )
        {
            renderOffscreen();
        }

        return thumbnail;
    }

    public BufferedImage getItemPicture()
    {
        if ( buffer == null )
        {
            renderOffscreen();
        }

        return buffer;
    }

    @Override
    protected void paintComponent( Graphics g )
    {
        if ( isVisible() )
        {
            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHints( hints );

            g2.setColor( Color.WHITE );
            g2.fillRect( 0, 0, getWidth(), getHeight() );
            g2.drawImage( getItemPicture(), 0, 0, this );

            g2.setColor( Color.DARK_GRAY );
        }
    }

    private static BufferedImage getPhotoBackground()
    {
        if ( shadowBuffer == null )
        {
            ShadowFactory factory = new ShadowFactory( SHADOW_SIZE, 0.17f, Color.BLACK );
            BufferedImage image = new BufferedImage( ITEM_WIDTH + INNER_MARGIN.left + INNER_MARGIN.right,
                    ITEM_HEIGHT + INNER_MARGIN.top + INNER_MARGIN.bottom,
                    BufferedImage.TYPE_INT_ARGB );
            Graphics2D g2 = image.createGraphics();
            g2.setColor( Color.WHITE );
            g2.fillRect( 0, 0, image.getWidth(), image.getHeight() );
            g2.dispose();

            shadowBuffer = factory.createShadow( image );
        }
        return shadowBuffer;
    }

    public PageLayoutTemplate getPageLayout()
    {
        return this.pageLayout;
    }

    public void setPageLayout( PageLayoutTemplate layout )
    {
        this.pageLayout = layout;
    }

    public void dragGestureRecognized( DragGestureEvent dge )
    {
        if ( DragAndDropLock.isLocked() )
        {
            DragAndDropLock.setDragAndDropStarted( false );
            return;
        }
        DragAndDropLock.setLocked( true );
        DragAndDropLock.setDragAndDropStarted( true );

        dge.startDrag( Cursor.getDefaultCursor(),
                pageLayout,
                this );

        Frame mainWindow = WindowManager.getDefault().getMainWindow();
        GhostGlassPane glassPane = new GhostGlassPane();
        if ( mainWindow instanceof JFrame )
        {
            JFrame mainFrame = (JFrame) mainWindow;
            GhostGlassPane.setOriginalGlassPane( mainFrame.getRootPane().getGlassPane() );
            mainFrame.getRootPane().setGlassPane( glassPane );
        }

        glassPane.setVisible( true );

        Point p = (Point) dge.getDragOrigin().clone();
        SwingUtilities.convertPointToScreen( p, this );
        SwingUtilities.convertPointFromScreen( p, glassPane );

        glassPane.setPoint( p );
        glassPane.setImage( getThumbnail(), getThumbnail().getWidth() );
        glassPane.repaint();
    }

    public void dragMouseMoved( DragSourceDragEvent dsde )
    {
        if ( !DragAndDropLock.isDragAndDropStarted() )
        {
            return;
        }

        GhostGlassPane glassPane = null;
        Frame mainWindow = WindowManager.getDefault().getMainWindow();
        if ( mainWindow instanceof JFrame )
        {
            JFrame mainFrame = (JFrame) mainWindow;
            if ( mainFrame.getRootPane().getGlassPane() instanceof GhostGlassPane )
            {
                glassPane = (GhostGlassPane) mainFrame.getRootPane().getGlassPane();
            }
        }

        if ( glassPane != null )
        {
            Point p = (Point) dsde.getLocation().clone();
            SwingUtilities.convertPointFromScreen( p, glassPane );
            glassPane.setPoint( p );
            glassPane.repaint( glassPane.getRepaintRect() );
        }
    }

    public void dragEnter( DragSourceDragEvent dsde )
    {
    }

    public void dragOver( DragSourceDragEvent dsde )
    {
    }

    public void dropActionChanged( DragSourceDragEvent dsde )
    {
    }

    public void dragExit( DragSourceEvent dse )
    {
    }

    public void dragDropEnd( DragSourceDropEvent dsde )
    {
        if ( !DragAndDropLock.isDragAndDropStarted() )
        {
            return;
        }
        DragAndDropLock.setDragAndDropStarted( false );

        GhostGlassPane glassPane = null;
        Frame mainWindow = WindowManager.getDefault().getMainWindow();
        if ( mainWindow instanceof JFrame )
        {
            JFrame mainFrame = (JFrame) mainWindow;
            if ( mainFrame.getRootPane().getGlassPane() instanceof GhostGlassPane )
            {
                glassPane = (GhostGlassPane) mainFrame.getRootPane().getGlassPane();
            }
        }

        Point p = (Point) dsde.getLocation().clone();
        SwingUtilities.convertPointFromScreen( p, glassPane );

        if ( !dsde.getDropSuccess() )
        {
            Point end = (Point) getLocation().clone();
            SwingUtilities.convertPointToScreen( end, this.getParent() );
            SwingUtilities.convertPointFromScreen( end, glassPane );


            end.x += getWidth() / 2;
            end.y += getHeight() / 2;

            Timer backTimer = new Timer( 1000 / 60, new TravelBackToOrigin( glassPane, p, end ) );
            backTimer.start();
        }
        else
        {
            DragAndDropLock.setLocked( false );
            glassPane.setImage( null );
            glassPane.setVisible( false );
            glassPane.setPoint( p );
            glassPane.repaint( glassPane.getRepaintRect() );
        }
    }

    // XXX: should use acceleration instead of time
    // XXX: travel path should be a curve
    private class TravelBackToOrigin implements ActionListener
    {

        private boolean isInitialized;
        private long start;
        private Point startPoint;
        private Point endPoint;
        private GhostGlassPane glassPane;
        private static final double INITIAL_SPEED = 500.0;
        private static final double INITIAL_ACCELERATION = 6000.0;

        private TravelBackToOrigin( GhostGlassPane glassPane, Point start, Point end )
        {
            this.glassPane = glassPane;
            this.startPoint = start;
            this.endPoint = end;
            isInitialized = false;
        }

        public void actionPerformed( ActionEvent e )
        {
            if ( !isInitialized )
            {
                isInitialized = true;
                start = System.currentTimeMillis();
            }

            long elapsed = System.currentTimeMillis() - start;
            double time = (double) elapsed / 1000.0;

            double a = (endPoint.y - startPoint.y) / (double) (endPoint.x - startPoint.x);
            double b = endPoint.y - a * endPoint.x;

            int travelX = (int) (INITIAL_ACCELERATION * time * time / 2.0 + INITIAL_SPEED * time);
            if ( startPoint.x > endPoint.x )
            {
                travelX = -travelX;
            }

            int travelY = (int) ((startPoint.x + travelX) * a + b);
            int distanceX = Math.abs( startPoint.x - endPoint.x );

            if ( Math.abs( travelX ) >= distanceX )
            {
                ((Timer) e.getSource()).stop();

                glassPane.setPoint( endPoint );
                glassPane.repaint( glassPane.getRepaintRect() );

                SwingUtilities.invokeLater( new Runnable()
                {

                    public void run()
                    {
                        glassPane.setImage( null );
                        glassPane.setVisible( false );
                    }
                } );
                DragAndDropLock.setLocked( false );

                return;
            }

            glassPane.setPoint( new Point( startPoint.x + travelX,
                    travelY ) );

            glassPane.repaint( glassPane.getRepaintRect() );
        }
    }
}
